#include "TweakerTool.h"
#include "MoveCmd.h"

#include <QDebug>

TweakerTool::TweakerTool(DiagramMan& dman)
:	Tool(dman),
	_defaultState(this), _reselectState(this), _movingState(this),
	_state(&_defaultState)
{
}

bool TweakerTool::mouseMoveEvent(QMouseEvent& ev)
{
	return _state->mouseMoveEvent(ev);
}

bool TweakerTool::mousePressEvent(QMouseEvent& ev)
{
	return _state->mousePressEvent(ev);
}

bool TweakerTool::mouseReleaseEvent(QMouseEvent& ev)
{
	return _state->mouseReleaseEvent(ev);
}

// functor
class object_contains
{
	public:
		object_contains(const QPoint& pt)
		: _pt(pt)
		{
		}

		bool operator()(const UMLObject* object)
		{
			return QRect(object->position(), object->size()).contains(_pt);
		}

	private:
		QPoint _pt;
};

TweakerTool::State::State(TweakerTool* context)
:	_context(context)
{
}

TweakerTool::DefaultState::DefaultState(TweakerTool* context)
:	State(context)
{
}

void TweakerTool::DefaultState::takeControl()
{
	qDebug("DefaultState taking over!");

	context()._state = this;
}

bool TweakerTool::DefaultState::mouseMoveEvent(QMouseEvent&)
{
	return false;
}

bool TweakerTool::DefaultState::mousePressEvent(QMouseEvent& ev)
{
	DiagramFam& active = context().dman().active();

	QPoint logical = context().tological(ev.pos());

	// clear selection if CTRL is not held
	if (!ev.modifiers().testFlag(Qt::ControlModifier))
		active.canvas().clearSelection();

	object_contains pred(logical);

	/* Note: use rbegin() so that we select the most-recently-placed object
	 * first, a bit more user-friendly. */

	// Find the first object under cursor
	UMLDiagram::reverse_iterator i = find_if(
			active.diagram().rbegin(), active.diagram().rend(), pred);

	if (i != active.diagram().rend())
	{
		active.canvas().select(*i);
		context()._reselectState.takeControl(*i);
	}

	return true;
}

bool TweakerTool::DefaultState::mouseReleaseEvent(QMouseEvent&)
{
	return false;
}

/*** ReselectState ***/

TweakerTool::ReselectState::ReselectState(TweakerTool* context)
:	State(context),
	_last(NULL)
{
}

void TweakerTool::ReselectState::takeControl(UMLObject* last)
{
	qDebug("ReselectState taking over!");

	if (!last)
		qWarning("ReselectState got a NULL last object. Weird.");

	_last = last;
	context()._state = this;
}

bool TweakerTool::ReselectState::mouseMoveEvent(QMouseEvent& ev)
{
	if (!ev.buttons())
	{
		context()._defaultState.takeControl();
		return true;
	}
	else if (ev.buttons().testFlag(Qt::LeftButton))
	{
		context()._movingState.takeControl(_last, ev.pos());
		return true;
	}

	return false;
}

bool TweakerTool::ReselectState::mousePressEvent(QMouseEvent&)
{
	return false;
}

bool TweakerTool::ReselectState::mouseReleaseEvent(QMouseEvent& ev)
{
	DiagramFam& active = context().dman().active();

	QPoint logical = context().tological(ev.pos());

	// clear selection if CTRL is not held
	if (!ev.modifiers().testFlag(Qt::ControlModifier))
		active.canvas().clearSelection();

	object_contains pred(logical);

	// Find the first object under cursor. Use rbegin() so that we select the
	// most-recently-placed object first to be a bit more user-friendly.
	UMLDiagram::reverse_iterator first = find_if(
			active.diagram().rbegin(), active.diagram().rend(), pred);

	// Find the iterator for _last, if it's under the cursor
	UMLDiagram::reverse_iterator i;
	for (i = first;
		 i != active.diagram().rend() && *i != _last;
		 ++i)
		;

	// i now either points to _last or == rend()

	// Find one past _last
	if (i != active.diagram().rend())
	{
		i = find_if(++i, active.diagram().rend(), pred);

		// If there is none past _last, loop: use first
		if (i == active.diagram().rend())
			i = first;
	}

	if (i != active.diagram().rend())
	{
		_last = *i;
		active.canvas().select(*i);
	}
	else
		context()._defaultState.takeControl();

	return true;
}

/*** MovingState ***/

TweakerTool::MovingState::MovingState(TweakerTool* context)
:	State(context),
	_target(NULL)
{
}

void TweakerTool::MovingState::takeControl(UMLObject* target,
		const QPoint& initial)
{
	qDebug() << "MovingState taking over from initial " << initial;

	if (!target)
		qWarning("MovingState given NULL target. Hmph.");

	_target = target;
	_previous = context().tological(initial);

	context()._state = this;
}

bool TweakerTool::MovingState::mouseMoveEvent(QMouseEvent& ev)
{
	sendCommand(ev.pos(), true);
	
	return true;
}

bool TweakerTool::MovingState::mousePressEvent(QMouseEvent&)
{
	qWarning("MovingState got a mouse press, weird.");

	return false;
}

bool TweakerTool::MovingState::mouseReleaseEvent(QMouseEvent& ev)
{
	sendCommand(ev.pos(), false);

	context()._defaultState.takeControl();

	return true;
}

void TweakerTool::MovingState::sendCommand(const QPoint& mouse, bool transitive)
{
	/* Use deltas from previous mouse position so that dragging from the
	 * bottom-right corner will move from that corner, not forcing the mouse to
	 * jump to top-left.
	 *
	 * Note that we use logical positions to only send commands when the
	 * position really changes.
	 */
	
	QPoint logical = context().tological(mouse);
	QPoint delta = logical - _previous;

	if (!delta.isNull())
	{
		MoveCmd cmd(_target, _target->position() + delta);
		context().dman().active().acceptor().accept(
				cmd, transitive);

		_previous = logical;
	}
}

/*** TEMPLATE ***/

/*
bool TweakerTool::ReselectState::mouseMoveEvent(QMouseEvent&)
{
	return false;
}

bool TweakerTool::ReselectState::mousePressEvent(QMouseEvent&)
{
	return false;
}

bool TweakerTool::ReselectState::mouseReleaseEvent(QMouseEvent&)
{
	return false;
}
*/

